package org.fujene.memman;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.logging.Logger;

import org.fujene.memcon.ReplicateIndexMemory;
import org.fujene.memcon.IndexDisk;
import org.fujene.structure.Constants;
import org.fujene.structure.ContentIDs;
import org.fujene.structure.ContentInfo;
import org.fujene.structure.ExtendQueue;
import org.fujene.structure.ExtendTerm;
import org.fujene.structure.HashID;
import org.fujene.structure.IndexQueue;
import org.fujene.structure.LookupPointer;
import org.fujene.toolbox.ContentHash;

public class ReplicateMemoryManager {
	// ネットワークを利用する向けのインデックスシステムを分離
	private static final int DIGLENGTH = Constants.SKIPPTR_INTERVAL;
	private static final int MEM_SKIPPTR = Constants.MEMORY_TYPE_SKIPPTR;
	private static final int MEM_SKIPCONT = Constants.MEMORY_TYPE_SKIPPTRCONT;

	// メモリ・ディスク本体
	private ReplicateIndexMemory memory = null;
	private IndexDisk disk = null;

	public boolean stopFlag = false;

	private int indexcounter = 0;
	private int termcounter = 0;
	private int searchcounter = 0;

	private int contentNum = 1;
	private int supplementNum = 0;

	private ContentInfo contentInfo = null;
	private Logger logger = null;

	public ReplicateMemoryManager(int blockSize, int blockLines, String dirPath,
			Logger logger, ContentInfo contentInfo) {
		this.disk = new IndexDisk(dirPath);
		this.contentInfo = contentInfo;
		this.memory = new ReplicateIndexMemory(blockSize, blockLines, disk,
				contentInfo, logger);
		this.logger = logger;

		logger.fine("Memory manager started.");
	}

	public void storeTerm(String term, long ID, int termfreq, int valueType) {
		try {
			// 辞書しまう(あれば追加しない)
			ExtendTerm newDic = storeExtentDictionary(term, valueType);

			// DocIDリストしまう
			storeInvIndexExtent(newDic.ptr, ID, termfreq, valueType);
		} catch (UnsupportedEncodingException ignored) {
		}
	}

	// 転置インデックスのポインタを取得
	public LookupPointer searchTerm(String term, int valueType) {
		// 単語検索
		ExtendTerm newTerm = lookupTermExtent(term, valueType);

		LookupPointer dictPtr = newTerm.ptr;
		if (dictPtr != null)
			return memory.lookupDictionaryInvIndex(dictPtr, valueType);
		else
			return null;
	}

	public ExtendQueue lookupContent(long ID, int priorIndex) {
		int i;
		ExtendQueue returnContents = new ExtendQueue(contentNum, supplementNum, ID);
		LookupPointer priorPtr = null;
		for (i = 0; i < contentNum; i++) {
			ContentIDs ptrInfo = lookupContentPointerExtent(ID, i);
			if (ptrInfo.currentPtr != null) {
				if(i == priorIndex)
					priorPtr = ptrInfo.currentPtr;
				returnContents.contents[i] = memory.lookupContentsExtent(ptrInfo.currentPtr);
			} else 
				return null;
		}

		if(priorPtr != null){
			LookupPointer[] supplementPointers = memory.lookupPureContentPointer(priorPtr);
			for (i = 0; i < supplementPointers.length; i++)
				returnContents.supplements[i] = memory.lookupContentsExtent(supplementPointers[i]);
		}
		
		return returnContents;
	}

	public ArrayList<ExtendTerm> getTermList(int fromHash, int toHash, int valueType) {
		ArrayList<ExtendTerm> terms = new ArrayList<ExtendTerm>(1000);

		// スキップポインタからスタート
		LookupPointer firstPtr = memory.lookupSkipPointer(0);
		LookupPointer nextPtr = firstPtr;
		ExtendTerm queue;
		long hashCode;

		while (nextPtr != null) {
			firstPtr = nextPtr;
			queue = memory.lookupDictionary(firstPtr, valueType);
			hashCode = ContentHash.getTermHash(queue.term);
			if (ContentHash.compareRange(hashCode, fromHash, toHash))
				terms.add(queue);
			nextPtr = memory.lookupDictionaryNext(firstPtr);
		}

		return terms;
	}

	public ArrayList<ExtendQueue> getContentList(int hashFrom, int hashTo, int priorIndex) {
		ArrayList<ExtendQueue> terms = new ArrayList<ExtendQueue>(1000);
		int i;
		int supp = 0;
		LookupPointer[] firstPtrs = new LookupPointer[contentNum];
		LookupPointer[] nextPtrs = new LookupPointer[contentNum];
		LookupPointer[] supplementPtrs = null;
		IndexQueue queue;
		ExtendQueue newTerm;
		
		for (i = 0; i < contentNum; i++) {
			// スキップポインタからスタート
			firstPtrs[i] = memory.lookupContentsSkipPointer(0, i);
			nextPtrs[i] = firstPtrs[i];
		}

		while(nextPtrs[i] != null){
			newTerm = new ExtendQueue(contentNum, supplementNum, -1);
			supp = supplementNum;
			for (i = 0; i < contentNum; i++) {
				if (nextPtrs[i] == null){
					supp = 0;
					break;
				}
				firstPtrs[i] = nextPtrs[i];
				queue = memory.lookupContentsWithID(firstPtrs[i]);
				if (ContentHash.compareRange(queue.hash, hashFrom, hashTo))
					newTerm.contents[i] = queue.value;
				nextPtrs[i] = memory.lookupNextContentPointer(firstPtrs[i]);
				if(priorIndex == i)
					supplementPtrs = memory.lookupPureContentPointer(firstPtrs[i]);
			}
			
			for (i = 0; i < supp; i++) {
				newTerm.supplements[i] = memory.lookupPureContent(supplementPtrs[i]);
			}
		}

		return terms;
	}

	public int getCounter() {
		try {
			return indexcounter;
		} catch (Exception e) {
			return -1;
		}
	}

	public int getTerms() {
		try {
			return termcounter;
		} catch (Exception e) {
			e.printStackTrace();
			return -1;
		}
	}

	public int getSearchcounter() {
		return searchcounter;
	}

	public void getMemoryList() {
		memory.showMemoryElapsed();
	}

	public void initializeMemory() {
		try {
			memory.readMemory();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void saveMemory() {
		memory.saveMemories();
	}

	public int getSkipPointerLen() {
		return memory.getSkipPointerLength();
	}

	private ExtendTerm lookupTermExtent(String query, int valueType) {
		// スキップポインタを見る
		int skipPointerLength = memory.getSkipPointerLength();
		LookupPointer dicPtr = null;
		LookupPointer nextPtr = null;
		ExtendTerm storedTerm = null;
		ExtendTerm returnTerm = null;
		int i;
		int skipLevel = 0;
		int skipVal = 1;
		if (skipPointerLength > 1) {
			skipLevel = (int) (Math.log(skipPointerLength - 1) / Math
					.log(DIGLENGTH));
			skipVal = (int) Math.pow(DIGLENGTH, skipLevel);
		}
		for (i = 0; i < skipPointerLength; i += skipVal) {
			dicPtr = memory.lookupSkipPointer(i);
			// ポインタなし
			if (dicPtr == null)
				break;

			storedTerm = memory.lookupDictionary(dicPtr, valueType);
			storedTerm.skipptr_index = i;
			int comp = storedTerm.term.compareTo(query);
			if (comp > 0) {
				// storeしてるほうが上 -> これより前
				skipLevel--;
				if (skipLevel == -1 || i == 0) {
					i--;
					break;
				}
				i -= skipVal;
				skipVal = (int) Math.pow(DIGLENGTH, skipLevel);

			} else if (comp == 0) {
				// まさにこの単語
				return storedTerm;
			}

			// 後ろを見る
			if (i + skipVal >= skipPointerLength) {
				do {
					skipLevel--;
					if (skipLevel == -1) {
						skipVal = 1;
						break;
					}
					skipVal = (int) Math.pow(DIGLENGTH, skipLevel);
				} while (i + skipVal >= skipPointerLength);
			}
		}
		if (i == -1) {
			// 一番先頭！ -> storedTermに意味はなくなる
			// 次の単語
			nextPtr = storedTerm.ptr;
			storedTerm = null;
		} else {
			// もう一度スキップポインタ取り出し
			if (i == skipPointerLength)
				i--;
			dicPtr = memory.lookupSkipPointer(i);
			storedTerm = memory.lookupDictionary(dicPtr, valueType);
		}

		// 辞書順検索
		if (storedTerm != null) {
			LookupPointer prevPtr = storedTerm.ptr;
			ExtendTerm nextTerm = memory.lookupDictionary(
					storedTerm.next, valueType);
			int count = 0;
			while (nextTerm != null) {
				int comp = nextTerm.term.compareTo(query);
				if (comp > 0) {
					// nextTermが上 -> 辞書になかった
					boolean addSkipPtr = count >= DIGLENGTH ? true : false;
					returnTerm = new ExtendTerm(query, null, prevPtr,
							nextTerm.ptr, i, addSkipPtr);
					break;
				} else if (comp == 0) {
					// ↓はいらないかも
					nextTerm.prev = prevPtr;
					return nextTerm;
				} else {
					prevPtr = nextTerm.ptr;
					nextTerm = memory.lookupDictionary(nextTerm.next,
							valueType);
				}
				count++;
			}

			if (returnTerm == null) {
				// 最後の単語だった
				boolean addSkipPtr = count >= DIGLENGTH ? true : false;
				returnTerm = new ExtendTerm(query, null, prevPtr, null, i,
						addSkipPtr);
			}
		} else {
			// 一番先頭(i = -1) or 最初の単語(i = 0)
			returnTerm = new ExtendTerm(query, null, null, nextPtr, i, false);
		}

		return returnTerm;
	}

	private void makeSkipPtrForTerm(ExtendTerm term, int type) {
		int index = term.skipptr_index;

		if (index == -1) {
			// 最初の部分書き換え
			memory.storeSkipPointer(0, type, term.ptr, true);
				refreshSkipPtrDict();
		} else {
			if (term.needSkipPtr) {
				// 追加
				memory.storeSkipPointer(index + 1, type, term.ptr, false);
			}
		}
	}

	private void makeSkipPtrForContent(ContentIDs term, int type) {
		int index = term.skipPtrIndex;

		if (index == -1) {
			// 最初の部分書き換え
			memory.storeSkipPointer(0, type, term.currentPtr, true);
			refreshSkipPtrContent();
		} else {
			if (term.addSkipPtr) {
				// 追加
				memory.storeSkipPointer(index + 1, type, term.currentPtr, false);
			}
		}
	}

	// 1番目のスキップポインタに固まらない対策
	private void refreshSkipPtrDict() {
		LookupPointer threshold = memory.lookupSkipPointer(1);
		LookupPointer nextPtr = memory.lookupSkipPointer(0);
		LookupPointer prevPtr = null;
		int count = 0;
		if (threshold == null) {
			while (nextPtr != null) {
				prevPtr = nextPtr;
				nextPtr = memory.lookupDictionaryNext(nextPtr);
				count++;
			}
		} else {
			while (threshold.equals(nextPtr)) {
				prevPtr = nextPtr;
				nextPtr = memory.lookupDictionaryNext(nextPtr);
				count++;
			}
		}

		if (count >= DIGLENGTH) {
			memory.storeSkipPointer(1, MEM_SKIPPTR, prevPtr, false);
		}
	}

	// 1番目のスキップポインタに固まらない対策
	private void refreshSkipPtrContent() {
		LookupPointer threshold = memory.lookupSkipPointer(1);
		LookupPointer nextPtr = memory.lookupSkipPointer(0);
		LookupPointer prevPtr = null;
		int count = 0;
		if (threshold == null) {
			while (nextPtr != null) {
				prevPtr = nextPtr;
				nextPtr = memory.lookupNextContentPointer(nextPtr);
				count++;
			}
		} else {
			while (threshold.equals(nextPtr)) {
				prevPtr = nextPtr;
				nextPtr = memory.lookupNextContentPointer(nextPtr);
				count++;
			}
		}

		if (count >= DIGLENGTH) {
			memory.storeSkipPointer(1, MEM_SKIPCONT, prevPtr, false);
		}
	}

	// 辞書をストア
	private ExtendTerm storeExtentDictionary(String query, int valueType)
			throws UnsupportedEncodingException {
		ExtendTerm newTerm = lookupTermExtent(query, valueType);

		if (newTerm.ptr == null) {
			// 単語追加
			LookupPointer newTermPtr = memory.storeDictionary(
					newTerm.term, newTerm.next);
			newTerm.ptr = newTermPtr;

			// ポインタ接続
			if (newTerm.prev != null)
				memory.storeDictionaryPointer(newTerm.prev,
						newTermPtr);

			// スキップポインタ追加
			makeSkipPtrForTerm(newTerm, MEM_SKIPPTR);

			this.termcounter++;
		}

		return newTerm;
	}

	// 本文ポインタ取り出し
	public ArrayList<Long> getContentsByHash(LookupPointer ptr, int length) {
		ArrayList<Long> hashList = new ArrayList<Long>();
		LookupPointer currentPtr = ptr;
		LookupPointer lastPtr = null;
		HashID hash;
		long firstID = ptr.firstID;
		long lastDiff = 0;
		long tmpHash = 0;

		if (ptr.equals(Constants.emptyPointer) || firstID == -1) {
			return null;
		}

		for (int i = 0; i < length && currentPtr != null; i++) {
			hash = memory.lookupHashID(currentPtr);
			tmpHash = firstID - lastDiff;
			lastDiff += hash.ID;
			hashList.add(tmpHash);

			lastPtr = currentPtr;
			currentPtr = memory.lookupNextInvIndexExtent(lastPtr);
		}

		// <!> ptrの中身を破壊的に変更
		if (currentPtr != null) {
			ptr.block = lastPtr.block;
			ptr.point = lastPtr.point;
			ptr.firstID = tmpHash;
		} else {
			ptr.block = -1;
			ptr.point = -1;
			ptr.firstID = -1;
		}

		return hashList;
	}

	private void storeInvIndexExtent(LookupPointer dicPtr, long ID,
			int termfreq, int valueType) {
		// 転置インデックスのポインタ取得
		LookupPointer prevPtr = memory.lookupDictionaryInvIndex(dicPtr,
				valueType);

		// ID差分計算
		long firstID = memory.lookupDictionaryFirstID(dicPtr, valueType);
		long diff;
		if (firstID == -1)
			diff = 0;
		else
			diff = ID - firstID;

		// 転置インデックス書き込み
		LookupPointer invIndexPtr = memory.storeInvertIndex(prevPtr,
				diff, termfreq);

		// 辞書ポインタ更新
		memory.storeDictionaryToInvIndex(dicPtr, invIndexPtr, ID,
				valueType);
	}

	// 本文を探すことになります
	private ContentIDs lookupContentPointerExtent(long ID, int valueType) {
		// スキップポインタを見る
		int skipPointerLength = memory.getSkipContentsLength(valueType);
		long nextID;
		LookupPointer nextPtr = null;
		LookupPointer prevPtr = null;
		int i = 0;
		int skipLevel = 0;
		int skipVal = 1;
		if (skipPointerLength > 1) {
			skipLevel = (int) (Math.log(skipPointerLength - 1) / Math
					.log(DIGLENGTH));
			skipVal = (int) Math.pow(DIGLENGTH, skipLevel);
		}
		for (i = 0; i < skipPointerLength; i += skipVal) {
			nextPtr = memory.lookupContentsSkipPointer(i, valueType);
			// ポインタなし
			if (nextPtr == null)
				break;

			nextID = memory.lookupContentID(nextPtr);
			if (nextID > ID) {
				// 自分より前
				skipLevel--;
				if (skipLevel == -1 || i == 0) {
					i--;
					break;
				}
				i -= skipVal;
				skipVal = (int) Math.pow(DIGLENGTH, skipLevel);

			} else if (nextID == ID) {
				// まさにこの文章
				return new ContentIDs(ID, nextPtr, null, null, false, i);
				
			}

			// 後ろを見る
			if (i + skipVal >= skipPointerLength) {
				do {
					skipLevel--;
					if (skipLevel == -1) {
						skipVal = 1;
						break;
					}
					skipVal = (int) Math.pow(DIGLENGTH, skipLevel);
				} while (i + skipVal >= skipPointerLength);
			}
		}

		if (i == -1) {
			// 一番先頭！
			return new ContentIDs(ID, null, null, memory.lookupContentsSkipPointer(0,
					valueType), true, 0);
		} else {
			// もう一度スキップポインタ取り出し
			if (i == skipPointerLength)
				i--;
			nextPtr = memory.lookupContentsSkipPointer(i, valueType);
		}

		// 辞書順検索
		int count = 0;

		prevPtr = nextPtr;
		while (nextPtr != null) {
			long l = memory.lookupContentID(nextPtr);
			if (l > ID) {
				// nextTermが上 -> 辞書になかった
				return new ContentIDs(-1, null, prevPtr, nextPtr,
						(count >= DIGLENGTH), i);
			} else if (l == ID) {
				// IDが一致した
				return new ContentIDs(l, nextPtr, prevPtr, null, false, i);
			} else {
				prevPtr = nextPtr;
				nextPtr = memory.lookupNextContentPointer(nextPtr);
			}
			count++;
		}

		return new ContentIDs(ID, null, prevPtr, nextPtr, (count >= DIGLENGTH), i);
	}

	// 本文をメモリにストア
	public void storeContent(String content, long ID, int index) {
		ContentIDs contTerm = lookupContentPointerExtent(ID, index);

		// IDがかぶったということは→ 書き換えないで破棄
		if (contTerm.currentPtr == null) {
			LookupPointer storePtr = memory.storeContents(content, ID,
					index, contTerm.nextPtr);
			contTerm.currentPtr = storePtr;

			// 前からポインタ接続
			if (contTerm.prevPtr != null) {
				memory.storeContentNext(contTerm.prevPtr, storePtr);
			}

			// スキップポインタ追加
			makeSkipPtrForContent(contTerm, index);
		}
	}

	// 本文をメモリにストア
	public LookupPointer storePureContent(String content, long ID, int index) {
		// IDがかぶったということは→ 書き換えないで破棄
		return memory.storePureContent(content);
	}

	// 削除DocIDを追加
	public void deleteContent(long ID, int index) {
		ContentIDs infos = lookupContentPointerExtent(ID, index);
		if (infos.currentPtr != null) {
			// TODO: 削除操作
		}
	}
}
